﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Aquiles.Model;
using Aquiles.Helpers.Encoders;
using Aquiles.Command;
using Aquiles;
using Aquiles.Command.System;

namespace VersoSys.Data
{
    /// <summary>
    /// TODO
    /// 
    ///    -	Public listing of tags, public listing of bookmarks. If the user doesn’t have is private checked then put the bookmark and tags in the public so all can see this data
    /// </summary>
    public class BookmarkRepository
    {
        private const string KeyspaceName = "Booky";
        private const string ClusterName = "TestCluster1";
        public const string PublicTagName_TagUserLine = "__public__";
        public const string PublicBookmarkUserName_UserToBookmark = "__public__";
        public const string PublicUsername = "__public__";
        public const string NoTag = "__notag__";
        private const string BookmarkToTagFamilyName = "BookmarkToTag";
        private const string BookmarkToTagPublicFamilyName = "BookmarkToTagPublic"; 

        private const string BookmarksFamilyName = "Bookmarks";
        private const string TagsFamilyName = "Tags";
        private const string UserToBookmarkFamilyName = "UserToBookmark";
        private const string UserFamilyName = "Users";        
        private const string TimeLineFamilyName = "TimeLine";
        private const string UserLineFamilyName = "UserLine";
        private const string TagUserLineFamilyName = "TagUserLine";
        private const string TagUserLinePublicFamilyName = "TagUserLinePublic";
        private const string FollowingFamilyName = "Following";
        private const string FollowersFamilyName = "Followers";
        private const string TweetsFamilyName = "Tweets";
        
        private const int MaxTimelineTweets = 150;
        private const int MaxRelatedUsers = 5000;
        private const string PublicTimeLineUser = "!PUBLIC!";

        public BookmarkRepository()
        {
            Keyspace = new KeySpace(ClusterName, KeyspaceName);
        }

        internal KeySpace Keyspace { get; set; }

        public void CreateSchema()
        {
            //       <Keyspace Name="Chirper">
            //<ColumnFamily Name="Users" CompareWith="UTF8Type" />
            //<ColumnFamily Name="Tweets" CompareWith="UTF8Type" />
            //<ColumnFamily Name="Following" CompareWith="UTF8Type" />
            //<ColumnFamily Name="Followers" CompareWith="UTF8Type" />
            //<ColumnFamily Name="TimeLine" CompareWith="UTF8Type" />
            //<ColumnFamily Name="UserLine" CompareWith="UTF8Type" />
            //<ColumnFamily Name="Bookmarks" CompareWith="UTF8Type" />
            //<ColumnFamily Name="Tags" CompareWith="UTF8Type" />
            //<ColumnFamily Name="BookmarkToTag" CompareWith="UTF8Type" />
            //<ColumnFamily Name="UserToBookmark" CompareWith="UTF8Type" />

            Dictionary<string, AquilesColumnFamily> columnFamilies = new Dictionary<string, AquilesColumnFamily>();
            string keyspaceName = KeyspaceName;
            string clusterName = ClusterName;

            AquilesColumnFamily UsersColumnFamily = new AquilesColumnFamily();
            UsersColumnFamily.Name = "Users";
            UsersColumnFamily.Keyspace = keyspaceName;
            UsersColumnFamily.Comparator = "UTF8Type";
            UsersColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily TweetsColumnFamily = new AquilesColumnFamily();
            TweetsColumnFamily.Name = "Tweets";
            TweetsColumnFamily.Keyspace = keyspaceName;
            TweetsColumnFamily.Comparator = "UTF8Type";
            TweetsColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily FollowingColumnFamily = new AquilesColumnFamily();
            FollowingColumnFamily.Name = "Following";
            FollowingColumnFamily.Keyspace = keyspaceName;
            FollowingColumnFamily.Comparator = "UTF8Type";
            FollowingColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily FollowersColumnFamily = new AquilesColumnFamily();
            FollowersColumnFamily.Name = "Followers";
            FollowersColumnFamily.Keyspace = keyspaceName;
            FollowersColumnFamily.Comparator = "UTF8Type";
            FollowersColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily TimeLineColumnFamily = new AquilesColumnFamily();
            TimeLineColumnFamily.Name = "TimeLine";
            TimeLineColumnFamily.Keyspace = keyspaceName;
            TimeLineColumnFamily.Comparator = "UTF8Type";
            TimeLineColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily UserLineColumnFamily = new AquilesColumnFamily();
            UserLineColumnFamily.Name = "UserLine";
            UserLineColumnFamily.Keyspace = keyspaceName;
            UserLineColumnFamily.Comparator = "UTF8Type";
            UserLineColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily TagUserLineColumnFamily = new AquilesColumnFamily();
            TagUserLineColumnFamily.Name = "TagUserLine";
            TagUserLineColumnFamily.Keyspace = keyspaceName;
            TagUserLineColumnFamily.Comparator = "UTF8Type";
            TagUserLineColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily TagUserLinePublicColumnFamily = new AquilesColumnFamily();
            TagUserLinePublicColumnFamily.Name = "TagUserLinePublic";
            TagUserLinePublicColumnFamily.Keyspace = keyspaceName;
            TagUserLinePublicColumnFamily.Comparator = "UTF8Type";
            TagUserLinePublicColumnFamily.Type = AquilesColumnFamilyType.Standard;


            AquilesColumnFamily BookmarksColumnFamily = new AquilesColumnFamily();
            BookmarksColumnFamily.Name = "Bookmarks";
            BookmarksColumnFamily.Keyspace = keyspaceName;
            BookmarksColumnFamily.Comparator = "UTF8Type";
            BookmarksColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily TagsColumnFamily = new AquilesColumnFamily();
            TagsColumnFamily.Name = "Tags";
            TagsColumnFamily.Keyspace = keyspaceName;
            TagsColumnFamily.Comparator = "UTF8Type";
            TagsColumnFamily.Type = AquilesColumnFamilyType.Standard;

            //AquilesColumnFamily BookmarkToTagColumnFamily = new AquilesColumnFamily();
            //BookmarkToTagColumnFamily.Name = "BookmarkToTag";
            //BookmarkToTagColumnFamily.Keyspace = keyspaceName;
            //BookmarkToTagColumnFamily.Comparator = "TimeUUIDType";//UTF8Type
            //BookmarkToTagColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily BookmarkToTagColumnFamily = new AquilesColumnFamily();
            BookmarkToTagColumnFamily.Name = "BookmarkToTag";
            BookmarkToTagColumnFamily.Keyspace = keyspaceName;
            BookmarkToTagColumnFamily.Comparator = "UTF8Type";//
            BookmarkToTagColumnFamily.Type = AquilesColumnFamilyType.Standard;

            AquilesColumnFamily BookmarkToTagPublicColumnFamily = new AquilesColumnFamily();
            BookmarkToTagPublicColumnFamily.Name = "BookmarkToTagPublic";
            BookmarkToTagPublicColumnFamily.Keyspace = keyspaceName;
            BookmarkToTagPublicColumnFamily.Comparator = "UTF8Type";//
            BookmarkToTagPublicColumnFamily.Type = AquilesColumnFamilyType.Standard;

            //TODO remove this eventually
            AquilesColumnFamily UserToBookmarkColumnFamily = new AquilesColumnFamily();
            UserToBookmarkColumnFamily.Name = "UserToBookmark";
            UserToBookmarkColumnFamily.Keyspace = keyspaceName;
            UserToBookmarkColumnFamily.Comparator = "UTF8Type";
            UserToBookmarkColumnFamily.Type = AquilesColumnFamilyType.Standard;

            //AquilesColumnFamily UserToTagColumnFamily = new AquilesColumnFamily();
            //UserToTagColumnFamily.Name = "UserToTag";
            //UserToTagColumnFamily.Keyspace = keyspaceName;
            //UserToTagColumnFamily.Comparator = "UTF8Type";
            //UserToTagColumnFamily.Type = AquilesColumnFamilyType.Standard;


            columnFamilies.Add(TagUserLinePublicColumnFamily.Name, TagUserLinePublicColumnFamily);
            columnFamilies.Add(BookmarkToTagPublicColumnFamily.Name, BookmarkToTagPublicColumnFamily);

            columnFamilies.Add(UsersColumnFamily.Name, UsersColumnFamily);
            columnFamilies.Add(TweetsColumnFamily.Name, TweetsColumnFamily);
            columnFamilies.Add(FollowingColumnFamily.Name, FollowingColumnFamily);
            columnFamilies.Add(FollowersColumnFamily.Name, FollowersColumnFamily);
            columnFamilies.Add(TimeLineColumnFamily.Name, TimeLineColumnFamily);
            columnFamilies.Add(UserLineColumnFamily.Name, UserLineColumnFamily);
            columnFamilies.Add(BookmarksColumnFamily.Name, BookmarksColumnFamily);
            columnFamilies.Add(TagsColumnFamily.Name, TagsColumnFamily);

            //Bookmark To Tag Column Family
            columnFamilies.Add(BookmarkToTagColumnFamily.Name, BookmarkToTagColumnFamily);
            //columnFamilies.Add(UserToTagColumnFamily.Name, UserToTagColumnFamily);
            columnFamilies.Add(UserToBookmarkColumnFamily.Name, UserToBookmarkColumnFamily);
            
            //Tag Userline Column Family
            columnFamilies.Add(TagUserLineColumnFamily.Name, TagUserLineColumnFamily);            

            //Get the Keyspace for Booky
            RetrieveKeyspacesCommand retrieveKeyspacesCmd = new RetrieveKeyspacesCommand();
            using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(clusterName))
            {
                connection.Execute(retrieveKeyspacesCmd);
            }

            //Check if the keyspace exists
            bool keyspaceExists = false;
            for (int i = 0; (!keyspaceExists && i < retrieveKeyspacesCmd.Keyspaces.Count); i++)
            {
                keyspaceExists = (retrieveKeyspacesCmd.Keyspaces[i].Name.CompareTo(keyspaceName) == 0);
            }

            //Add the keyspace and columns if the keyspace does not exist
            if (keyspaceExists)
            {
                DropKeyspaceCommand dropKeyspaceCommand = new DropKeyspaceCommand();
                dropKeyspaceCommand.Keyspace = keyspaceName;
                using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(clusterName))
                {
                    connection.Execute(dropKeyspaceCommand);
                }
            }

            AddKeyspaceCommand addKeyspaceCmd = new AddKeyspaceCommand();
            addKeyspaceCmd.KeyspaceDefinition = new AquilesKeyspace()
            {
                Name = keyspaceName,
                ReplicationFactor = 1,
                ColumnFamilies = columnFamilies
            };

            using (IAquilesConnection connection = AquilesHelper.RetrieveConnection(clusterName))
            {
                connection.Execute(addKeyspaceCmd);
            }
            //}
        }

        #region Bookmarks

        public Bookmark GetBookmark(string id)
        {
            if (!Keyspace.Exists(BookmarksFamilyName, id, BookmarkFamily.Id))
            {
                return null;
            }

            string sTags = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.Tags);
            string[] arTags = sTags.Split(',');

            return new Bookmark
            {
                Id = id,
                User = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.User) ?? string.Empty,
                Title = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.Title) ?? string.Empty,
                Description = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.Description) ?? string.Empty,
                Url = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.Url) ?? string.Empty,
                IsCurrentlyReading = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.IsCurrentlyReading) ?? string.Empty,
                IsPrivate = Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.IsPrivate) ?? string.Empty,
                //Tags = arTags,
                Time = TimestampToDate(Keyspace.GetValue(BookmarksFamilyName, id, BookmarkFamily.Time) ?? string.Empty)
            };
        }

        public Tag GetTagByTitle(string title)
        {
            IList<Tag> list = GetPublicTags();
            return list.ToList().Find(t => t.Title.ToLower() == title.ToLower());
        }

        public Tag GetTagId(string id)
        {
            if (!Keyspace.Exists(TagsFamilyName, id, TagFamily.Id))
            {
                return null;
            }

            return new Tag
            {
                Id = id,
                Title = Keyspace.GetValue(TagsFamilyName, id, TagFamily.Title) ?? string.Empty,
                User = Keyspace.GetValue(TagsFamilyName, id, TagFamily.User) ?? string.Empty,
                IsMainTag = Keyspace.GetValue(TagsFamilyName, id, TagFamily.IsMainTag) ?? string.Empty,
                Description = Keyspace.GetValue(TagsFamilyName, id, TagFamily.Description) ?? string.Empty,
                Time = TimestampToDate(Keyspace.GetValue(TagsFamilyName, id, TagFamily.Time) ?? string.Empty)

            };
        }     

        public IList<Tag> GetTags(string username)
        {
            return GetUserItems(username, TagUserLineFamilyName, c => GetTagId(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
        }

        public IList<Tag> GetAllPublicTags()
        {
            return GetPublicTags(PublicUsername);
        }

        public IList<Tag> GetPublicTags(string username)
        {
            return GetUserItems(username, TagUserLinePublicFamilyName, c => GetTagId(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
        }

        public bool TagExists(string username, string tag)
        {
            IList<Tag> list = GetTags(username);            
            return list.ToList().Exists(t => t.Title == tag);                            
        }

        public IList<Tag> GetMainTags()
        {
            IList<Tag> list = GetPublicTags();
            return list.ToList().FindAll(t => Boolean.Parse(t.IsMainTag) == true);
        }

        //TODO we need a way to get a listing of bookmarks for a tag.
        //SEE BookmarkToTag as this may be the solution
        // the other solution may be to have another link column family to link
        //the other thing we could do is to put the user guid as an underscore combo in the BookmarkToTag column family
        //Software_johnhaighsguid
        //then when getting the tags from BookmarkToTag column family only get the ones for the current user guid

        public IList<Bookmark> GetBookmarksByTag(string tag, string username) 
        {
            IList<Bookmark> list = GetUserItems(tag+"__"+username, BookmarkToTagFamilyName, c => GetBookmark(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
            return list;
        }

        public IList<Bookmark> GetAllPublicBookmarks()
        { 
            return GetPublicBookmarksByTag(NoTag);
        }

        public IList<Bookmark> GetPublicBookmarksByTag(string tag)
        {
            //In the future as we add more public user's when are tag cap count reach's 200,000 per user, then we add more public user's
            return GetPublicBookmarksByTag(tag, PublicUsername);
        }

        public IList<Bookmark> GetPublicBookmarksByTag(string tag, string username)
        {
            IList<Bookmark> list = GetUserItems(tag + "__" + username, BookmarkToTagPublicFamilyName, c => GetBookmark(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
            return list;
        }

        public IList<Bookmark> GetBookmarksByUsername(string username)
        {
            IList<Bookmark> list = GetUserItems(username, UserToBookmarkFamilyName, c => GetBookmark(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
            return list;
        }

        /// <summary>
        /// GetPublicBookmarks - Gets a IList of public bookmarks
        /// </summary>
        /// <returns></returns>
        public IList<Bookmark> GetPublicBookmarks()
        {
            //IList<Bookmark> list = GetBookmarksByUsername(PublicUsername);
            return GetAllPublicBookmarks();
        }

        /// <summary>
        /// GetPublicTags - Gets an IList of public tags
        /// </summary>
        /// <returns></returns>
        public IList<Tag> GetPublicTags()
        {
            return GetAllPublicTags();
        }

        public bool AddTag(Tag tag, out string TagId)
        {            
            //if (Keyspace.Exists(BookmarkToTagFamilyName, tag.Title + "__" + tag.User, BookmarkToTagFamily.TagTitle))
            if (TagExists(tag.User,tag.Title))
            {
                TagId = "";
                return false;
            }
            tag.Id = Guid.NewGuid().ToString();
            tag.Time = DateTime.UtcNow;
            var tagTimestamp = DateToTimestamp(tag.Time);
            Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.Id, tag.Id);
            //InsertOrUpdateTag(tag);

            Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.Title, tag.Title);
            Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.User, tag.User);
            Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.Description, tag.Description);
            Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.Time, tagTimestamp);
            Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.IsMainTag, tag.IsMainTag);
            //Add the tag to bookmark relationship entry
            
            //Insert User TagUserLine
            //family,key, column name, value
            Keyspace.Insert(TagUserLineFamilyName, tag.User, tagTimestamp, tag.Id);
            
            //Insert Public TagUserLine
            Keyspace.Insert(TagUserLinePublicFamilyName, PublicUsername, tagTimestamp, tag.Id);

            TagId = tag.Id;
            return true;
        }

        public string AddDefaultNewUserBookmark(string username)
        {
            Bookmark b = new Bookmark();
            b.Title = "Booky";
            b.Description = "A Bookmark Management Application";
            b.IsPrivate = "True";
            b.Url = "http://booky.ca";
            b.User = username;
            b.Tags = new string[] { "My First Tag" };

            string sTagId;
            Tag t = new Tag();
            t.Title = "My First Tag";
            t.Description = " ";
            t.User = username;
            t.IsMainTag = "false";
            
            AddBookmark(b);
            AddTag(t, out sTagId);
            return b.Id;
        }

        public string AddBookmark(Bookmark bookmark)
        {
            bookmark.Id = Guid.NewGuid().ToString();
            bookmark.Time = DateTime.UtcNow;

            string sIsCurrentlyReading = bookmark.IsCurrentlyReading ?? "False";
            string sIsPrivate = bookmark.IsPrivate ?? "False";
            string sDesc = bookmark.Description ?? " ";

            string sCommaTags = String.Join(",",bookmark.Tags);

            var bookmarkTimestamp = DateToTimestamp(bookmark.Time);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.Id, bookmark.Id);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.Title, bookmark.Title);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.Description, sDesc);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.Time, bookmarkTimestamp);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.User, bookmark.User);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.Url, bookmark.Url);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.Tags, sCommaTags);            
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.IsCurrentlyReading, sIsCurrentlyReading);
            Keyspace.Insert(BookmarksFamilyName, bookmark.Id, BookmarkFamily.IsPrivate, sIsPrivate);

            //TODO remove once new changes are complete
            //Insert User to Bookmarks for the current user
            Keyspace.Insert(UserToBookmarkFamilyName, bookmark.User, bookmarkTimestamp, bookmark.Id);            
            
            //Add the bookmark to the public listing if the user hasn't marked the bookmark as public
            if (!Boolean.Parse(bookmark.IsPrivate))
            {
                //TODO remove once new changes are complete
                Keyspace.Insert(UserToBookmarkFamilyName, PublicBookmarkUserName_UserToBookmark, bookmarkTimestamp, bookmark.Id);
            }
            //Relate the Bookmark to the tags
            //Insert BookmarkToTag for tags
            if (bookmark.Tags != null)
            {
                if (bookmark.Tags.Length > 0)
                {
                    foreach (string item in bookmark.Tags)
                    {
                        //Insert User Bookmark To Tag Public 
                        Keyspace.Insert(BookmarkToTagFamilyName, item + "__" + bookmark.User, bookmarkTimestamp, bookmark.Id);
                        //Insert Public Bookmark To Tag Public 
                        if (!Boolean.Parse(bookmark.IsPrivate))
                        {
                            Keyspace.Insert(BookmarkToTagPublicFamilyName, item + "__" + PublicUsername, bookmarkTimestamp, bookmark.Id);
                        }
                        //Keyspace.Insert(BookmarkToTagFamilyName, item + "__" + bookmark.User, BookmarkToTagFamily.TagTitle, item);                                               
                    }                    
                }
            }
          
            //Insert User BookmarkToTag for default/all "__notag__"
            Keyspace.Insert(BookmarkToTagFamilyName, NoTag + "__" + bookmark.User, bookmarkTimestamp, bookmark.Id);

            if (!Boolean.Parse(bookmark.IsPrivate))
            {
                Keyspace.Insert(BookmarkToTagPublicFamilyName, NoTag + "__" + PublicUsername, bookmarkTimestamp, bookmark.Id);
            }

            return bookmark.Id;
        }

        #endregion

        #region Twitter

        public IList<Tweet> GetPublicTimeLine()
        {
            return GetTimeLine(PublicTimeLineUser);
        }

        public IList<Tweet> GetTimeLine(string userName)
        {
            return GetUserItems(
              userName, TimeLineFamilyName, c => GetTweet(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
        }

        public IList<Tweet> GetUserLine(string userName)
        {
            return GetUserItems(
              userName, UserLineFamilyName, c => GetTweet(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)), MaxTimelineTweets, (x, y) => x.Time.CompareTo(y.Time));
        }

        public IList<Following> GetFollowing(string userName)
        {
            return GetUserItems(
              userName,
              FollowingFamilyName,
              c => new Following { User = GetUser(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.ColumnName)), Since = TimestampToDate(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)) },
              MaxRelatedUsers,
              (f1, f2) => f1.Since.CompareTo(f2.Since));
        }

        public IList<Follower> GetFollowers(string userName)
        {
            return GetUserItems(
              userName,
              FollowersFamilyName,
              c => new Follower { User = GetUser(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.ColumnName)), Since = TimestampToDate(ByteEncoderHelper.UTF8Encoder.FromByteArray(c.Value)) },
              MaxRelatedUsers,
              (f1, f2) => f1.Since.CompareTo(f2.Since));
        }

        public bool Follow(string follower, string following)
        {
            if (string.Equals(follower, following))
            {
                return false;
            }

            var followerUser = GetUser(follower);
            var followingUser = GetUser(following);
            if (followerUser == null || followingUser == null)
            {
                return false;
            }

            var followTimestamp = DateToTimestamp(DateTime.UtcNow);
            if (!Keyspace.Exists(FollowersFamilyName, following, follower))
            {
                Keyspace.Insert(FollowersFamilyName, following, follower, followTimestamp);
            }

            if (!Keyspace.Exists(FollowingFamilyName, follower, following))
            {
                Keyspace.Insert(FollowingFamilyName, follower, following, followTimestamp);
            }

            return true;
        }

        public bool UnFollow(string follower, string following)
        {
            throw new NotImplementedException();
        }

        public Tweet GetTweet(string id)
        {
            if (!Keyspace.Exists(TweetsFamilyName, id, TweetsFamily.Id))
            {
                return null;
            }

            return new Tweet
            {
                Id = id,
                User = Keyspace.GetValue(TweetsFamilyName, id, TweetsFamily.User) ?? string.Empty,
                InReplyToTweet = Keyspace.GetValue(TweetsFamilyName, id, TweetsFamily.InReplyToTweet) ?? string.Empty,
                InReplyToUser = Keyspace.GetValue(TweetsFamilyName, id, TweetsFamily.InReplyToUser) ?? string.Empty,
                Location = Keyspace.GetValue(TweetsFamilyName, id, TweetsFamily.Location) ?? string.Empty,
                Text = Keyspace.GetValue(TweetsFamilyName, id, TweetsFamily.Text) ?? string.Empty,
                Time = TimestampToDate(Keyspace.GetValue(TweetsFamilyName, id, TweetsFamily.Time) ?? string.Empty)
            };
        }

        public string AddTweet(Tweet tweet)
        {
            tweet.Id = Guid.NewGuid().ToString();
            tweet.Time = DateTime.UtcNow;
            var tweetTimestamp = DateToTimestamp(tweet.Time);
            Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.Id, tweet.Id);
            Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.Location, tweet.Location);
            Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.Text, tweet.Text);
            Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.Time, tweetTimestamp);
            Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.User, tweet.User);
            if (!string.IsNullOrEmpty(tweet.InReplyToTweet) && !string.IsNullOrEmpty(tweet.InReplyToUser))
            {
                Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.InReplyToTweet, tweet.InReplyToTweet);
                Keyspace.Insert(TweetsFamilyName, tweet.Id, TweetsFamily.InReplyToUser, tweet.InReplyToUser);
            }

            Keyspace.Insert(UserLineFamilyName, tweet.User, tweetTimestamp, tweet.Id);
            Keyspace.Insert(TimeLineFamilyName, tweet.User, tweetTimestamp, tweet.Id);
            Keyspace.Insert(TimeLineFamilyName, PublicTimeLineUser, tweetTimestamp, tweet.Id);

            foreach (var follower in GetFollowers(tweet.User))
            {
                Keyspace.Insert(TimeLineFamilyName, follower.User.Name, tweetTimestamp, tweet.Id);
            }

            return tweet.Id;
        }

        #endregion

        #region User

        public User GetUser(string userName)
        {
            //if (!Keyspace.Exists(UserFamilyName, userName, UserFamily.Name))
            //{
            //    return null;
            //}

            User u = new User();
            u.Name = userName;
            u.PasswordHash = Keyspace.GetValue(UserFamilyName, userName, UserFamily.PasswordHash);
            u.Bio = Keyspace.GetValue(UserFamilyName, userName, UserFamily.Bio);
            u.DisplayName = Keyspace.GetValue(UserFamilyName, userName, UserFamily.DisplayName);
            u.Email = Keyspace.GetValue(UserFamilyName, userName, UserFamily.Email);
            u.Location = Keyspace.GetValue(UserFamilyName, userName, UserFamily.Location);
            u.Web = Keyspace.GetValue(UserFamilyName, userName, UserFamily.Web);
            u.CreatedAt = TimestampToDate(Keyspace.GetValue(UserFamilyName, userName, UserFamily.CreatedAt));
            
            return u;
        }

        public bool AddUser(User user)
        {
            if (Keyspace.Exists(UserFamilyName, user.Name, UserFamily.Name))
            {
                return false;
            }

            Keyspace.Insert(UserFamilyName, user.Name, UserFamily.Name, user.Name);
            user.CreatedAt = DateTime.UtcNow;

            //Add a default starting bookmark for the user
            AddDefaultNewUserBookmark(user.Name);

            //TODO Insert user add audit event
            //TODO call the API add audit event

            InsertOrUpdateUser(user);
            return true;
        }

        public bool UpdateUser(User user)
        {
            if (!Keyspace.Exists(UserFamilyName, user.Name, UserFamily.Name))
            {
                return false;
            }

            InsertOrUpdateUser(user);
            return true;
        }

        private List<T> GetUserItems<T>(string userName, string joinFamily,
            Converter<AquilesColumn, T> getValueFromColumn,
      int maxItems, Comparison<T> comparison)
        {
            var items = Keyspace.GetSlice(joinFamily, userName, maxItems).ConvertAll(getValueFromColumn);
            items.Sort(comparison);
            return items;
        }

        private void InsertOrUpdateUser(User user)
        {
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.Bio, user.Bio);
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.CreatedAt, DateToTimestamp(user.CreatedAt));
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.DisplayName, user.DisplayName);
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.Email, user.Email);
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.Location, user.Location);
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.Web, user.Web);
            InsertIfNotNullOrEmpty(UserFamilyName, user.Name, UserFamily.PasswordHash, user.PasswordHash);
        }

        private void InsertOrUpdateTag(Tag tag)
        {
            InsertIfNotNullOrEmpty(TagsFamilyName, tag.Id, TagFamily.Title, tag.Title);
            InsertIfNotNullOrEmpty(TagsFamilyName, tag.Id, TagFamily.User, tag.User);
            InsertIfNotNullOrEmpty(TagsFamilyName, tag.Id, TagFamily.Time, DateToTimestamp(tag.Time));

            //Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.Title, tag.Title);
            //Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.User, tag.User);
            //Keyspace.Insert(TagsFamilyName, tag.Id, TagFamily.Time, tagTimestamp);

            //Add the tag to bookmark relationship entry

            //Insert TagUserLine
            //family,key, column name, value
//            Keyspace.Insert(TagUserLineFamilyName, tag.User, tagTimestamp, tag.Id);
            InsertIfNotNullOrEmpty(TagUserLineFamilyName, tag.User, DateToTimestamp(tag.Time), tag.Id);
        }

        private void InsertOrUpdateBookmark(Bookmark bookmark)
        { 
        
        }

        #endregion

        private void InsertIfNotNullOrEmpty(string familyName, string key, string columnName, string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                Keyspace.Insert(familyName, key, columnName, value);
            }
        }

        private static DateTime TimestampToDate(string timestamp)
        {
            return string.IsNullOrEmpty(timestamp)
                     ? DateTime.MinValue
                     : new DateTime(Convert.ToInt64(timestamp), DateTimeKind.Utc);
        }

        private static string DateToTimestamp(DateTime date)
        {
            return date.ToUniversalTime().Ticks.ToString();
        }
    }
}
